﻿// Net_Course_Design.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#define SERVER_IP_ADDR "192.168.43.158"  
#define SERVER_PORT 8080
#define BACKLOG 10//最大等待连接数（排队人数）
#define BUF_SIZE 8192
#define OK 1
#define ERROR 0


#include <direct.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> 
#include <time.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

const char* Server_name = "Server: Web Server 1.0 - Xiaodu\r\n";

// 函数声明
int Server_Socket_Init(int port);
int Handle_Request_Message(char* message, int Socket);
int Judge_URI(char* URI, int Socket);
int Send_Ifon(int Socket, const char* sendbuf, int Length);
int Error_Request_Method(int Socket);
int Inquire_File(char* URI);
int File_not_Inquire(int Socket);
int Send_File(char* URI, int Socket);

const char* Judge_Method(char* method, int Socket);
const char* Judge_File_Type(char* URI, const char* content_type);
const char* Get_Data(const char* cur_time);
const char* Post_Value(char* message);

// 初始化服务器套接字
int Server_Socket_Init(int port) 
{
    WORD wVersionrequested;
    WSADATA wsaData;
    SOCKET ServerSock;
    struct sockaddr_in ServerAddr;
    int rval;

    //加载Winsock
    wVersionrequested = MAKEWORD(2, 2);
    if (WSAStartup(wVersionrequested, &wsaData) != 0) 
    {
        printf("Failed to load Winsock!\n");
        system("pause");
        return -1;
    }
    printf("Succeed to load Winsock!\n");

    //创建套接字
    ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ServerSock == INVALID_SOCKET) 
    {
        printf("Failed to create socket!\n");
        system("pause");
        exit(1);
    }
    printf("Succeed to create socket!\n");

    //配置服务器ip，端口
    memset(&ServerAddr, 0, sizeof(struct sockaddr));
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(port);
    ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);  
    //inet_addr(SERVER_IP_ADDR)（仅支持IPv4）
    //inet_addr是IPv4地址转换函数，将IPv4地址从点分十进制（如"192.168.43.158"）转换为32位二进制网络字节序值

    //绑定
    rval = bind(ServerSock, (SOCKADDR*)&ServerAddr, sizeof(struct sockaddr));
    if (rval == SOCKET_ERROR) 
    {
        printf("Failed to bind stream socket!\n");
        system("pause");
        exit(1);
    }
    printf("Succeed to bind stream socket!\n");

    return ServerSock;
}

// 处理HTTP请求报文
// Handle_Request_Message(revbuf, MessageSock)
int Handle_Request_Message(char* message, int Socket) 
{
    int rval = 0;
    char Method[BUF_SIZE] = { 0 };
    char URI[BUF_SIZE] = { 0 };
    char Version[BUF_SIZE] = { 0 };

    //从字符串message中读取格式化输入[请求方法（Method）请求路径（URI） HTTP版本（Version）】
       
    if (sscanf(message, "%s %s %s", Method, URI, Version) != 3) 
    {
        printf("Request line error!\n");
        return ERROR;
    }

    if (Judge_Method(Method, Socket) == ERROR) 
    {
        return ERROR;
    }
    else if (strcmp(Judge_Method(Method, Socket), "POST") == 0) 
    {
        Post_Value(message);
    }

    if (strcmp(URI, "/") == 0) 
    {
        strcpy(URI, "\\xiaodu.html");//将路径 \xiaodu.html存入URI数组
    }

    if (Judge_URI(URI, Socket) == ERROR) 
    {
        return ERROR;
    }
    else 
    {
        rval = Send_File(URI, Socket);
    }

    if (rval == OK) 
    {
        printf("The process is successfully finished!\n");
    }

    return OK;
}

// 判断请求方法（GET/POST）
const char* Judge_Method(char* method, int Socket) 
{
    if (strcmp(method, "GET") == 0) {
        return "GET";
    }
    else if (strcmp(method, "POST") == 0) {
        return "POST";
    }
    else {
        Error_Request_Method(Socket);//处理客户端使用了服务器不支持的方法
        return ERROR;
    }
}

// 判断URI是否有效
int Judge_URI(char* URI, int Socket) 
{
    if (Inquire_File(URI) == ERROR) {
        File_not_Inquire(Socket);
        return ERROR;
    }
    else {
        return OK;
    }
}

// 发送数据到客户端
//例：Send_Ifon(Socket, cur_time, strlen(cur_time)
int Send_Ifon(int Socket, const char* sendbuf, int Length) {
    int sendtotal = 0, bufleft, rval = 0;
    bufleft = Length;
    while (sendtotal < Length) {
        rval = send(Socket, sendbuf + sendtotal, bufleft, 0);
        if (rval < 0) {
            if (errno == EINTR) continue; // 信号中断，重试发送
            else if (errno == EAGAIN) break; // 非阻塞模式，需等待可写
            else break; // 其他错误，终止
        }
        if (rval == 0) break; // 连接关闭，终止发送
        sendtotal += rval;//已发送的字节数
        bufleft -= rval;//剩余需要发送的字节数
    }
    return rval < 0 ? ERROR : OK;
}

// 处理501错误
//处理客户端使用了服务器不支持的HTTP方法（如PUT、DELETE等）时，返回501 Not Implemented错误
int Error_Request_Method(int Socket) 
{
    const char* Method_err_line = "HTTP/1.1 501 Not Implemented\r\n";
    const char* cur_time = "";
    const char* Method_err_type = "Content-type: text/plain\r\n";
    const char* File_err_length = "Content-Length: 41\r\n";
    const char* Method_err_end = "\r\n";
    const char* Method_err_info = "The request method is not yet completed!\r\n";

    if (Send_Ifon(Socket, Method_err_line, strlen(Method_err_line)) == ERROR) return ERROR;
    if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) return ERROR;
    cur_time = Get_Data(cur_time);
    Send_Ifon(Socket, "Data: ", 6);
    if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) return ERROR;
    if (Send_Ifon(Socket, Method_err_type, strlen(Method_err_type)) == ERROR) return ERROR;
    if (Send_Ifon(Socket, Method_err_end, strlen(Method_err_end)) == ERROR) return ERROR;
    if (Send_Ifon(Socket, Method_err_info, strlen(Method_err_info)) == ERROR) return ERROR;

    return OK;
}

// 检查文件是否存在
int Inquire_File(char* URI) 
{
    char cwd[BUF_SIZE];
    if (_getcwd(cwd, sizeof(cwd)) == NULL) return ERROR;
    char abs_path[BUF_SIZE];
    //路径拼接，%s\\%s确保使用一致的反斜杠[\]
    snprintf(abs_path, sizeof(abs_path), "%s\\%s", cwd, URI);
    //调试： 添加日志输出，打印正在尝试访问的文件路径
   // printf("Debug: Checking file at path: %s\n", abs_path);
    struct stat File_info;
    return (stat(abs_path, &File_info) == -1) ? ERROR : OK;
    //系统调用 stat() 会尝试访问 abs_path 指定的文件路径，并返回该路径的元数据到File_info结构体
}



// 处理404错误（文件不存在）
int File_not_Inquire(int Socket) 
{
    const char* File_err_line = "HTTP/1.1 404 Not Found\r\n";
    const char* cur_time = "";
    const char* File_err_type = "Content-type: text/html\r\n";
    const char* File_err_end = "\r\n";


    FILE* file;
    struct stat file_stat;
    char sendbuf[BUF_SIZE];
    int send_length;

    // Get the current working directory
    char cwd[BUF_SIZE];
    if (_getcwd(cwd, sizeof(cwd)) == NULL) {
        perror("getcwd() error");
        return ERROR;
    }

    // Construct the absolute path to 404.html
    char abs_path[BUF_SIZE];
    snprintf(abs_path, sizeof(abs_path), "%s\\404.html", cwd);

    // Open and read 404.html
    file = fopen(abs_path, "rb");
    if (file != NULL) 
    {
        fstat(_fileno(file), &file_stat);

        // Send 404 Not Found response
        if (Send_Ifon(Socket, File_err_line, strlen(File_err_line)) == ERROR) {
            printf("Sending file_error_line error!\n");
            fclose(file);
            return ERROR;
        }

        if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {
            printf("Sending Server_name failed!\n");
            fclose(file);
            return ERROR;
        }

        cur_time = Get_Data(cur_time);
        Send_Ifon(Socket, "Date: ", 6);
        if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {
            printf("Sending cur_time error!\n");
            fclose(file);
            return ERROR;
        }

        if (Send_Ifon(Socket, File_err_type, strlen(File_err_type)) == ERROR) {
            printf("Sending file_error_type error!\n");
            fclose(file);
            return ERROR;
        }

        // Calculate and send Content-Length
        char content_length[BUF_SIZE];
        snprintf(content_length, sizeof(content_length), "Content-Length: %lld\r\n", (long long)file_stat.st_size);
        if (Send_Ifon(Socket, content_length, strlen(content_length)) == ERROR) {
            printf("Sending file_error_length error!\n");
            fclose(file);
            return ERROR;
        }

       
        if (Send_Ifon(Socket, File_err_end, strlen(File_err_end)) == ERROR) {
            printf("Sending file_error_end error!\n");
            fclose(file);
            return ERROR;
        }

        // Send 404.html content
        while ((send_length = fread(sendbuf, 1, BUF_SIZE, file)) > 0) {
            if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {
                printf("Sending 404.html content error!\n");
                break;
            }
        }
        //printf("Succeeded to open 404.html!\n");
        fclose(file);
    }
    else 
    {
        printf("Failed to open 404.html!\n");
        return ERROR;
    }
   
    printf("Send 404.html !\n");
    return OK;
}

// 发送文件
int Send_File(char* URI, int Socket) 
{
    char cwd[BUF_SIZE];
    if (_getcwd(cwd, sizeof(cwd)) == NULL) //获取服务器工作目录路径
    {
        perror("getcwd() error");
        return ERROR;
    }

    char abs_path[BUF_SIZE];
    snprintf(abs_path, sizeof(abs_path), "%s\\%s", cwd, URI);//拼接路径——工作目录+需要发送的文件
    //将两个字符串参数 cwd（当前工作目录）和 URI（资源标识符）拼接，结果存入 abs_path 缓冲区
    
    // 200 OK响应
    const char* File_ok_line = "HTTP/1.1 200 OK\r\n";//基础响应头
    const char* cur_time = "";
    const char* File_ok_type = "";
    const char* File_ok_length = "Content-Length: ";
    const char* File_ok_end = "\r\n";

    FILE* file;
    struct stat file_stat;//struct stat 结构体系统提供，包含文件的详细信息
    char Length[BUF_SIZE];
    char sendbuf[BUF_SIZE];
    int send_length;

    //检查文件类型
    if (Judge_File_Type(abs_path, File_ok_type) == ERROR) {
        printf("The request file's type from client's request message is error!\n");
        return ERROR;
    }

    //打包 发送HTTP 响应头——服务器返回给客户端的“包裹标签”
    file = fopen(abs_path, "rb");
    if (file != NULL) 
    {
        //_fileno(file)
        //从文件流（FILE* 类型）中提取底层的操作系统文件描述符（int 类型）
        fstat(_fileno(file), &file_stat);
        //fstat(fd, &file_stat)
        //通过文件描述符 fd 查询文件的元数据（如大小、权限、时间戳等），并将结果存入 struct stat 结构体
        
        _itoa(file_stat.st_size, Length, 10);//将文件大小（file_stat.st_size）转换为指定进制（10）字符串放到Length数组中

        if (Send_Ifon(Socket, File_ok_line, strlen(File_ok_line)) == ERROR) {
            printf("Sending file_ok_line error!\n");
            return ERROR;
        }

        if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {
            printf("Sending Server_name failed!\n");
            return ERROR;
        }

        cur_time = Get_Data(cur_time);
        Send_Ifon(Socket, "Date: ", 6);
        if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {
            printf("Sending cur_time error!\n");
            return ERROR;
        }
        printf("Send cur_time：%s\n",cur_time);

        File_ok_type = Judge_File_Type(abs_path, File_ok_type);
        if (Send_Ifon(Socket, File_ok_type, strlen(File_ok_type)) == ERROR) {
            printf("Sending file_ok_type error!\n");
            return ERROR;
        }
        printf("Send type:%s", File_ok_type);

        if (Send_Ifon(Socket, File_ok_length, strlen(File_ok_length)) != ERROR) {
            if (Send_Ifon(Socket, Length, strlen(Length)) != ERROR) {
           
                if (Send_Ifon(Socket, "\r\n", 2) == ERROR) {
                    printf("Sending file_ok_length error!\n");
                    return ERROR;
                }
            }
        }
        //printf("File size : %lld bytes\n", (long long)file_stat.st_size);
        printf("File size : %s bytes\n", Length);



       
        if (Send_Ifon(Socket, File_ok_end, strlen(File_ok_end)) == ERROR) {
            printf("Sending file_ok_end error!\n");
            return ERROR;
        }

        //分批发送文件内容数据（每次最多读取1kb）
        while (file_stat.st_size > 0) {
            if (file_stat.st_size < 1024) {
                send_length = fread(sendbuf, 1, file_stat.st_size, file);
                if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {
                    printf("Sending file information error!\n");
                    continue;
                }
                file_stat.st_size = 0;
            }
            else {
                send_length = fread(sendbuf, 1, 1024, file);
                if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {
                    printf("Sending file information error!\n");
                    continue;
                }
                file_stat.st_size -= 1024;
            }
        }

        printf("Send file successfully\n");
    }
    else 
    {
        printf("The file is NULL!\n");
        return ERROR;
    }

    return OK;
}


const char* Judge_File_Type(char* URI, const char* content_type) {
    const char* suffix = strrchr(URI, '.'); // 查找最后一个点号的位置
    if (suffix == NULL) 
    {
        // 无扩展名，返回默认类型
        return "Content-Type: application/octet-stream\r\n";
    }
    suffix++; // 移动到扩展名的第一个字符（跳过点号）

    // 将扩展名转换为小写（处理大小写不敏感）
    char lower_ext[16];
    strncpy(lower_ext, suffix, sizeof(lower_ext) - 1);
    lower_ext[sizeof(lower_ext) - 1] = '\0'; // 确保字符串终止
    for (char* p = lower_ext; *p; ++p) 
    {
        *p = tolower(*p);
    }

    // 根据扩展名返回对应的Content-Type
    if (strcmp(lower_ext, "html") == 0) {
        return "Content-Type: text/html\r\n";
    }
    else if (strcmp(lower_ext, "jpg") == 0 || strcmp(lower_ext, "jpeg") == 0) {
        return "Content-Type: image/jpeg\r\n";
    }
    else if (strcmp(lower_ext, "png") == 0) {
        return "Content-Type: image/png\r\n";
    }
    else if (strcmp(lower_ext, "gif") == 0) {
        return "Content-Type: image/gif\r\n";
    }
    else if (strcmp(lower_ext, "txt") == 0) {
        return "Content-Type: text/plain; charset=utf-8\r\n";
    }
    else if (strcmp(lower_ext, "xml") == 0) {
        return "Content-Type: text/xml\r\n";
    }
    else if (strcmp(lower_ext, "rtf") == 0) {
        return "Content-Type: text/rtf\r\n";
    }
    else if (strcmp(lower_ext, "js") == 0) {
        return "Content-Type: application/javascript\r\n";
    }
    else if (strcmp(lower_ext, "css") == 0) {
        return "Content-Type: text/css; charset=utf-8\r\n";
    }
    else if (strcmp(lower_ext, "mp3") == 0) {
        return "Content-Type: audio/mpeg\r\n";
    }
    else if (strcmp(lower_ext, "mp4") == 0) {
        return "Content-Type: video/mp4\r\n";
    }
    else 
    {
        // 未知扩展名，返回默认类型
        return "Content-Type: application/octet-stream\r\n";
    }
}


// 获取当前时间
const char* Get_Data(const char* cur_time) 
{
    time_t curtime;
    time(&curtime);
    return ctime(&curtime);//获取当前系统时间，并将其转换为可读的字符串格式
}

// 提取POST请求数据
const char* Post_Value(char* message) {
    const char* suffix = strrchr(message, '\n') + 1;
    printf("\n\nPost Value: %s\n\n", suffix);
    return suffix;
}


// 主函数
int main(int argc, char* argv[]) 
{
    int port = SERVER_PORT;//默认端口8080

    SOCKET ServerSock, MessageSock;//ServerSock监听套接字，用于接受客户端连接
    //MessageSock：与客户端通信的套接字
    struct sockaddr_in ClientAddr;//存储客户端地址信息（IP和端口）
    int rval, Length;
    char revbuf[BUF_SIZE];//revbuf：接收客户端请求的缓冲区

    
    printf("Web Server is starting on port %d...\n\n", port);
    ServerSock = Server_Socket_Init(port);

    //服务器socket开始监听连接
    rval = listen(ServerSock, BACKLOG);
    if (rval == SOCKET_ERROR)
    {
        printf("Failed to listen socket!\n");
        system("pause");
        exit(1);
    }
    printf("Listening the socket on port %d...\n", port);

    printf("\n************************************************************\n");

    while (OK) 
    {
       
        //接受客户端请求建立连接
        Length = sizeof(struct sockaddr);
        MessageSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &Length);

        if (MessageSock == INVALID_SOCKET) //无效套接字
        {
            printf("Failed to accept connection from client!\n");
            system("pause");
            exit(1);
        }
        printf("Succeed to accept connection from [%s:%d] !\n\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
        //inet_ntoa 的实现：二进制 IP → 点分十进制    ntohs 的实现：网络字节序（端口） → 主机字节序

     
        //接受客户端请求数据
        memset(revbuf, 0, BUF_SIZE);//清空缓冲区，接收新数据
        rval = recv(MessageSock, revbuf, BUF_SIZE, 0);//从 MessageSock 套接字读取数据，存入 revbuf 缓冲区
        //返回值 rval 表示实际接收的字节数（可能小于 BUF_SIZE）


        if (rval > 0 && rval < BUF_SIZE) 
        {
            revbuf[rval] = '\0';//在接收数据末尾添加\0
        }

        if (rval <= 0)
            printf("Failed to receive request message from client!\n");
        else 
        {
            printf("%s\n", revbuf);
            //revbuf 缓冲区会接收客户端发来的完整HTTP请求报文
            rval = Handle_Request_Message(revbuf, MessageSock);
        }

        closesocket(MessageSock);
        printf("\n************************************************************\n\n\n");
    }

    closesocket(ServerSock);//关闭套接字
    WSACleanup();
    return OK;
}



// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件
